#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Util.hpp"
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <sys/wait.h>
#include <algorithm>
#include <cstdlib>
#include <fcntl.h>
#include <sys/sendfile.h>
#define SEP ": "
#define WEB_ROOT "/home/fertilizer/HTTP/wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"

#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500

static std::string Code2Desc(int code)
{
    std::string desc;
    switch (code)
    {
    case 200:
        desc = "OK";
        break;
    case 404:
        desc = "Not Found";
        break;
    default:
        break;
    }
    return desc;
}
static std::string Suffix2Desc(const std::string &suffix)
{
    std::unordered_map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".htm", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".json", "application/json"},
        {".xml", "application/xml"},

        {".jpg", "image/jpeg"},
        {".jpeg", "image/jpeg"},
        {".png", "image/png"},
        {".gif", "image/gif"},
        {".ico", "image/x-icon"},
        {".svg", "image/svg+xml"},

        {".mp4", "video/mp4"},
        {".mp3", "audio/mpeg"},
        {".wav", "audio/wav"},

        {".txt", "text/plain"},
        {".pdf", "application/pdf"},
        {".zip", "application/zip"},
        {".gz", "application/gzip"}};

    auto iter = suffix2desc.find(suffix);
    if (iter != suffix2desc.end())
    {
        return iter->second;
    }
    return "text/html";
};

class HttpRequest
{
public:
    std::string request_line;
    std::vector<std::string> request_header;
    std::string blank;
    std::string request_body;

    // 请求行解析结果
    std::string method;
    std::string uri;
    std::string version;

    // 正文解析结果
    std::unordered_map<std::string, std::string> header_kv;
    int content_length;

    std::string path;
    std::string suffix; // 后缀名
    std::string query_string;

    int size;
    bool cgi;
};

class HttpResponse
{
public:
    std::string status_line;
    std::vector<std::string> response_header;
    std::string blank;
    std::string response_body;
    int status_code;
    int fd;
    HttpResponse()
        : blank(LINE_END), status_code(OK), fd(-1)
    {
    }
    ~HttpResponse()
    {
    }
};

class EndPoint
{
private:
    int sock_;
    HttpRequest http_request;
    HttpResponse http_response;
    bool stop;

private:
    // ====================== 请求行 ======================

    // 从 socket 中读取一行作为 HTTP 请求行，例如：
    //   "GET /index.html HTTP/1.1"
    // 并存入 http_request.request_line
    bool RecvHttpRequestLine()
    {
        auto &line = http_request.request_line;
        if (Util::ReadLine(sock_, line) > 0)
        {
            line.resize(line.size() - 1);
            LOG(INFO, line);
        }
        else
        {
            stop = true;
        }
        return stop;
    }
    // ====================== 请求头 ======================

    // 逐行读取 HTTP 请求头，例如：
    //   "Host: 127.0.0.1:8080"
    //   "Content-Length: 20"
    // 碰到空行（"\n"）就停止
    bool RecvHttpRequestHeader()
    {
        std::string line;
        while (true)
        {
            line.clear();
            if (Util::ReadLine(sock_, line) <= 0)
            {
                stop = true;
                break;
            }
            if (line == "\n" || line == "\r\n")
            {
                http_request.blank = line;
                break;
            }
            line.resize(line.size() - 1);
            http_request.request_header.push_back(line);
            LOG(INFO, line);
        }
        return stop;
    }

    // ====================== 解析请求行 ======================

    // 把请求行解析成三个部分：方法、URI、版本
    // 例如："GET /index.html HTTP/1.1"
    // method=GET, uri=/index.html, version=HTTP/1.1
    void ParseHttpRequestLine()
    {
        auto &line = http_request.request_line;
        std::stringstream ss(line);
        ss >> http_request.method >> http_request.uri >> http_request.version;
        auto &method = http_request.method;
        std::transform(method.begin(), method.end(), method.begin(), ::toupper); // 转化为大写

        // LOG(INFO, http_request.method);
        // LOG(INFO, http_request.uri);
        // LOG(INFO, http_request.version);
    }

    // ====================== 解析请求头 ======================

    // 把每一行头部按 ": " 切分成 key-value 存入 map
    // 例如 "Content-Length: 20" => { "Content-Length", "20" }
    void ParseHttpRequestHeader()
    {
        std::string key;
        std::string value;
        for (auto &iter : http_request.request_header)
        {
            if (Util::CutString(iter, key, value, SEP))
            {
                http_request.header_kv.insert({key, value});
                // std::cout << "DEBUG-KEY: " << key << std::endl;
                // std::cout << "DEBUG-val: " << value << std::endl;
            }
        }
    }

    // ====================== 是否需要读正文 ======================

    // 判断是否需要读取请求正文（body）
    // 只有 POST 请求，并且头部有 Content-Length 才需要
    bool IsNeedRecvHttpRequestBody()
    {
        auto &method = http_request.method; // 提取方法
        if (method == "POST")
        {
            auto &header_kv = http_request.header_kv;
            auto iter = header_kv.find("Content-Length"); // 找到正文长度的kv
            if (iter != header_kv.end())
            {
                http_request.content_length = atoi(iter->second.c_str()); // 把长度提取出来
                return true;
            }
        }
        return false;
    }

    // ====================== 读取正文 ======================

    // 如果是 POST 且有 Content-Length，
    // 就循环从 socket 里读出 body 的数据
    bool RecvHttpRequestBody()
    {
        if (IsNeedRecvHttpRequestBody())
        {
            auto content_length = http_request.content_length;
            auto &body = http_request.request_body;
            char ch = 0;
            while (content_length)
            {
                ssize_t s = recv(sock_, &ch, 1, 0);
                if (s > 0)
                {
                    body.push_back(ch);
                    content_length--;
                }
                else
                {
                    stop = true;
                    break;
                }
            }
        }
        return stop;
    }

    // ====================== 处理 CGI 请求 ======================

    // 用 CGI 方式处理动态请求：
    // - 建立两个管道 input/output
    // - fork 出子进程，子进程 exec CGI 程序
    // - 父进程通过管道和 CGI 程序交互
    // GET：参数通过 QUERY_STRING 环境变量传递
    // POST：正文通过管道写给子进程 stdin
    int ProcessCgi()
    {
        LOG(INFO, "Use CGI Model");
        int code = OK;
        auto &method = http_request.method;
        auto &query_string = http_request.query_string;
        auto &body_text = http_request.request_body;
        auto &bin = http_request.path;
        int content_length = http_request.content_length;
        std::string &response_body = http_response.response_body;
        // 环境变量
        std::string query_string_env;
        std::string method_env;
        std::string content_length_env;
        int input[2];
        int output[2];

        if (pipe(input) < 0)
        {
            LOG(ERROR, "pipe input error");
            code = SERVER_ERROR;
            return code;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "pipe out error");
            code = SERVER_ERROR;
            return code;
        }

        pid_t pid = fork();
        if (pid == 0)
        {
            // child
            // 从child角度，input管道，child往里写,所以关闭0；
            close(input[0]);
            close(output[1]);

            // execl替换后，新程序里看不到旧变量，所以“记不住”哪些 fd 是干嘛的
            // 把管道 固定到标准流 0/1
            // 在 exec 之前把你要用的 fd 重定向到 STDIN/STDOUT/STDERR，新程序天然知道用 0/1/2。

            method_env = "METHOD=";
            method_env += method;
            putenv((char *)method_env.c_str());

            std::cout << "Debug Method: " << method_env << std::endl;
            std::cout << "Debug Bin: " << bin << std::endl;
            dup2(input[1], 1);
            dup2(output[0], 0);

            // 对于 GET 请求： 用环境变量传递参数
            //   - 参数拼接在 URI 后面 (?a=10&b=20)。
            //   - 父进程把解析到的 query_string 写入到 QUERY_STRING 环境变量。
            //   - 子进程执行时，可以通过 getenv("QUERY_STRING") 直接读取参数。
            if (method == "GET")
            {
                query_string_env += "QUERY_STRING=";
                query_string_env += query_string;
                putenv((char *)query_string_env.c_str());
            }
            // 对于 POST 请求： 用环境变量传递参数长度
            //   - 参数放在 HTTP 请求体(body)里。
            //   - 父进程把正文长度写入 CONTENT_LENGTH 环境变量。
            //   - 子进程执行时，可以通过 getenv("CONTENT_LENGTH") 读取正文
            //     然后从标准输入(stdin)读取指定长度的数据，拿到参数。
            else if (method == "POST")
            {
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(content_length);
                putenv((char *)content_length_env.c_str());
            }
            else
            {
                LOG(ERROR, "method is not right");
                exit(1);
            }

            execl(bin.c_str(), bin.c_str(), nullptr);

            exit(1);
        }
        else if (pid < 0)
        {
            LOG(ERROR, "fork error!");
            return 404;
        }
        else
        {
            // parent
            close(input[1]);
            close(output[0]);
            // 对于 POST 请求： 用管道传递参数
            //   - 参数放在 HTTP 请求体(body)里。
            //   - 父进程把 body 内容写入管道的写端(output[1])。
            //   - 子进程执行时，CGI 程序从标准输入(stdin)读取数据，拿到参数。
            if (method == "POST")
            {
                const char *start = body_text.c_str();
                int size = 0;
                int total = 0;
                while (total < content_length && (size = write(output[1], start + total, body_text.size() - total)) > 0)
                {
                    total += size;
                }
            }
            char c = 0;
            while (read(input[0], &c, 1) > 0)
            {
                response_body.push_back(c);
            }

            int status = 0;
            pid_t ret = waitpid(pid, &status, 0);
            if (ret == pid)
            {
                if (WIFEXITED(status))
                {
                    if (WEXITSTATUS(status) == 0)
                    {
                        code = OK;
                    }
                    else
                    {
                        code = BAD_REQUEST;
                    }
                }
                else
                {
                    code = SERVER_ERROR;
                }
            }

            close(input[1]);
            close(output[0]);
        }
        // std::cout << "debug: " << "Use CGI Model" << std::endl;
        return code;
    }

    // ====================== 处理非 CGI 请求 ======================

    // 打开文件（静态资源），准备生成 HTTP 响应
    int ProcessNonCgi()
    {
        http_response.fd = open(http_request.path.c_str(), O_RDONLY);
        if (http_response.fd >= 0)
        {
            return OK;
        }

        return 404;
    }
    // 构建404响应报头
    void HandlerError(std::string page)
    {
        http_request.cgi = false;
        http_response.fd = open(page.c_str(), O_RDONLY);
        if (http_response.fd >= 0)
        {
            // stat 用来获取文件信息
            struct stat st;
            stat(page.c_str(), &st);
            http_request.size = st.st_size;

            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);

            line = "Content-Length: ";
            line += std::to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
        }
    }
    // 构建200响应报头
    void BuildOkResponse()
    {
        std::string line = "Content-Type: ";
        line += Suffix2Desc(http_request.suffix);
        line += LINE_END;

        http_response.response_header.push_back(line);
        line = "Content-Length: ";
        if (http_request.cgi)
        {
            line += std::to_string(http_response.response_body.size());
        }
        else
        {
            line += std::to_string(http_request.size);
        }
        line += LINE_END;
        http_response.response_header.push_back(line);
    }
    // 根据状态码构建响应报头
    void BuildHttpResponHelper(int code)
    {
        std::string &status_line = http_response.status_line;
        std::string path = WEB_ROOT;
        path += "/";
        // 构建响应状态行
        // 1. HTTP/1.0 404 Not Found\r\n
        status_line = HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code);
        status_line += " ";
        status_line += Code2Desc(code);
        status_line += LINE_END;

        switch (code)
        {
        case OK:
            BuildOkResponse();
            break;
        case NOT_FOUND:
            path += PAGE_404;
            HandlerError(path);
            break;
        case BAD_REQUEST:
            path += PAGE_404;
            HandlerError(path);
            break;
        case SERVER_ERROR:
            path += PAGE_404;
            HandlerError(path);
            break;
        default:
            break;
        }
    }

public:
    EndPoint(int sock)
        : sock_(sock), stop(false)
    {
    }
    bool IsStop()
    {
        return stop;
    }
    void RecvHttpRequest()
    {
        if ((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
        {

            ParseHttpRequestLine();   // 解析请求行
            ParseHttpRequestHeader(); // 解析报文
            RecvHttpRequestBody();    // 接收正文
        }
    }

    void BuildHttpResponse()
    {
        struct stat st;
        int size = 0;
        std::string path_;
        size_t found;
        auto &code = http_response.status_code;
        // 判断方法是否正确
        if (http_request.method != "POST" && http_request.method != "GET")
        {
            LOG(WARNING, "method is not right");
            code = BAD_REQUEST;
            goto END;
        }

        // 如果是get
        // 1. 看uri里有没有"?"，有就切分成 path 和 query_string
        // 2. 没有就 path = uri
        if (http_request.method == "GET")
        {
            size_t pos = http_request.uri.find("?");
            if (pos != std::string::npos)
            {
                Util::CutString(http_request.uri, http_request.path, http_request.query_string, "?");
                http_request.cgi = true;
            }
            else
            {
                http_request.path = http_request.uri;
            }
        }
        // 如果是post
        // path = uri
        // cgi = true
        else if (http_request.method == "POST")
        {
            http_request.path = http_request.uri;
            http_request.cgi = true;
        }
        else
        {
        }
        // 1. 拼接本地文件路径
        path_ = http_request.path; // 1. http_request.path='/'         2. http_request.path='/a/b'
        http_request.path = WEB_ROOT;
        http_request.path += path_; // 1. wwwroot/ (目录,就要追加首页)    2. wwwroot/a/b

        // 2. 如果是目录，追加主页
        if (http_request.path[http_request.path.size() - 1] == '/')
        {
            http_request.path += HOME_PAGE;
        }
        // std::cout << "DEBUG : " << http_request.path << std::endl;

        // 3. 判断资源是否存在
        //    - 不存在，404
        //    - 存在
        //      - 普通文件，非 CGI
        //      - 可执行文件，CGI
        if (stat(http_request.path.c_str(), &st) == 0)
        {
            // 说明资源存在
            if (S_ISDIR(st.st_mode))
            {
                // 请求的资源是一个目录，且绝对不会以"/"结尾
                http_request.path += "/";
                http_request.path += HOME_PAGE;
                stat(http_request.path.c_str(), &st);
            }
            if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
            {
                // 文件或目录是否具有执行权限，特殊处理
                http_request.cgi = true;
            }
            size = st.st_size; // 普通文件大小,用于后续处理 Content-Length
            http_request.size = size;
        }
        else
        {
            // 说明资源不存在
            std::string info = http_request.path;
            info += " NOT FOUND!";
            LOG(WARNING, info);
            code = NOT_FOUND;
            goto END;
        }
        found = http_request.path.rfind(".");
        if (found == std::string::npos)
        {
            http_request.suffix = ".html";
        }
        else
        {
            http_request.suffix = http_request.path.substr(found);
        }
        if (http_request.cgi)
        {
            code = ProcessCgi();
        }
        else
        {
            code = ProcessNonCgi(); // 不是cgi，返回静态网页
        }

    END:
        BuildHttpResponHelper(code);
    }
    void SendHttpResponse()
    {
        // 1. 先把响应首行发出去，比如 "HTTP/1.0 200 OK\r\n"
        send(sock_, http_response.status_line.c_str(), http_response.status_line.size(), 0);

        // 2. 循环发所有响应头，每个都是一行 "Key: Value\r\n"
        for (auto iter : http_response.response_header)
        {
            send(sock_, iter.c_str(), iter.size(), 0);
        }

        // 3. 发一个空行 "\r\n"，表示头部结束，后面就是正文
        send(sock_, http_response.blank.c_str(), http_response.blank.size(), 0);

        // 4. 用 sendfile 直接把磁盘文件的内容（html、jpg、css等）零拷贝发送到 socket
        // 分fd 还是 response_body
        if (http_request.cgi)
        {
            auto &body = http_response.response_body;
            size_t size = 0;
            size_t total = 0;
            const char *start = body.c_str();
            while (total < body.size() && (size = send(sock_, start + total, body.size() - total, 0)) > 0)
            {
                total += size;
            }
        }
        else
        {
            sendfile(sock_, http_response.fd, NULL, http_request.size);
            close(http_response.fd);
        }
    }

    ~EndPoint()
    {
        close(sock_);
    }
};

class Callback
{
public:
    Callback()  
    {}
    void operator()(int sock_)
    {
        HandlerRequest(sock_);
    }
    void HandlerRequest(int sock)
    {
        LOG(INFO, "Handler Request Begin");
#ifdef DEBUG
        // TEST
        char buffer[1024];
        recv(sock, &buffer, sizeof(buffer), 0);
        std::cout << "----------------begin------------------" << std::endl;
        std::cout << buffer << std::endl;
        std::cout << "----------------end------------------" << std::endl;
#else

#endif
        EndPoint *ep = new EndPoint(sock);
        ep->RecvHttpRequest();
        if (!ep->IsStop())
        {
            ep->BuildHttpResponse();
            ep->SendHttpResponse();
        }
        else
        {
            LOG(WARNING, "Recv Error, Stop Process");
        }

        delete ep;
    }
    ~Callback() {}
};